Skip to content

Fixes #279 - #280

Merged
guijacquemet merged 4 commits into
mainfrom
claude/reconfirm-scope
Nov 11, 2026
Merged

guijacquemet merged 4 commits into
mainfrom
claude/reconfirm-scope

Conversation

@guijacquemet

Copy link
Copy Markdown
Contributor

Fixes #279. Base: main b6f821a (the merge of #278). One commit: a50cc93.

Summary

A §4.6 re-confirmation names an ID: (id_kind, old_id, new_id, confirmed_on, both SHA-256). It does not name a folder. So Yes, remove of one folder recorded every channel ID that folder held, and that record accepted any change of those IDs. That included a saved header name lost in another folder that is still there, which the user never saw.

  • description_removal.reconfirmations: a removal or rename answer now accepts a channel ID only when the channel has no other loss. A channel stays in open (publication refuses it as R_CONFIRMED_ID_CHANGED) when, besides what the answers touch, either of these holds:

    The folders and what was lost are returned in the new Reconfirmations.unseen.

  • description_reconcile: each unit still in the folder carries headers_changed, the saved channels whose header name at their saved position is not a saved one. It uses the same comparison as _channels_differ, except that an empty name also counts when the channel has saved names.

  • Notebook (Check and save): the line beside the disabled Save button names the channel and the folder, for example: "DNA lost a saved header name in ctrl/rep1, which is still there: a removal or rename does not confirm it (undo that tag on Channels)". The red R_CONFIRMED_ID_CHANGED at channels.DNA message above the button is unchanged. Nothing is confirmed for the user.

  • A header name held only by the removed folder is still covered by the removal: every folder still there gives a saved name.

Spec interpretation

  • The record cannot say "this ID, only in folder X". Experiment-v1 §4.6 keys reconfirmation by id_kind, old_id and new_id. The key set is fixed (RF4), and there is no folder or field key. So a channel with an unanswered change in a folder still there gets no record from the removal, and publication refuses it with R_CONFIRMED_ID_CHANGED (§4.6 (c)). I chose this safe side.
  • Refuse rather than offer a click for a lost header name. Today there is no confirmation for a lost header name, even without a removal: Fixes #258, fixes #252 #278 does not offer one as a move, and the candidate is refused with "Undo that change". A removal elsewhere now leaves the same refusal in place instead of lifting it. Moves keep their own explicit click (Notebook: no way to record a channel re-confirmation after re-ordering a unit's channels #258), so for moves the "shown, own click" branch applies.
  • An empty name now counts as a lost name (safe side). When a channel has saved names and a folder still there now gives no name at its position, the removal does not cover the lost name. Edge case: in a folder that never had names, a removal elsewhere that drops the only names now needs the tag change undone before saving.
  • CLI describe: it does not share this logic. It holds no answers, so it never builds removal or rename re-confirmations (--publish refuses every draft built from the folders), and nothing changes there.

Evidence

  • Regression test tests/test_reconfirm_scope.py::test_a_removal_does_not_accept_a_header_name_lost_in_a_folder_still_there[other-name|no-name]. Both cases fail on main (run against main's src) and pass here. The scenario:

    1. Save with ctrl/rep1 naming DNA "Hoechst".
    2. Rewrite that header to "Nuc" or to no name, and delete drug/rep2, which also holds DNA.
    3. Re-tag ctrl/rep1's channel 1 DNA, so "Hoechst" is no longer a DNA name.
    4. Click Yes, remove.

    Save is now disabled, save_experiment() refuses, the reason shows beside the button, and no record is written. On main it published with a DNA reconfirmation from the removal.

  • Guards, which pass on main and here:

    • Without the re-tag, the removal still saves.
    • A header name held only by the removed folder is still covered by the removal, with one DNA record.
  • Fixes #258, fixes #252 #278's tests all pass, including test_a_removal_elsewhere_does_not_confirm_a_move.

  • Byte-identical publishes, main vs this branch: the SHA-256 of morphobricks.toml, experiment_design.csv and morphobricks.records.jsonl is identical in five scenarios:

    • first save;
    • header rename via header_names;
    • confirmed removal;
    • confirmed move;
    • move plus removal.
  • Records files: the files written in those scenarios and by the new tests were checked with the Spec's tools/audit/refrecords.py check (MorphoBricks-Spec main b4b1008). All pass: "2 records OK" and "7 records OK" (×4).

  • Spec: tests/spec_fixtures/ and tests/test_spec_*.py are untouched. 1732 passed, 0 skipped.

Tests

python tools/check.py on a50cc93 passed:

  • ruff check: all checks passed;
  • ruff format --check: 437 files already formatted;
  • pytest: 5290 passed, 4 skipped;
  • code rules: passed.

The 4 skips are environment-only:

  • test_cli_log.py:166, test_folder_rules.py:199 and test_folder_rules.py:230: "this file system tells names apart by case";
  • test_discovery.py:665: "running with permissions that ignore file modes".

The widgets were tested headless only. They were not looked at in JupyterLab or Colab.


Generated by Claude Code

…ers (#279)

Section 4.6 keys a re-confirmation by ID (old ID, new ID, date), never by folder,
so the Yes, remove of one folder recorded every channel ID it held and that
record accepted any other change of the same ID: a saved header name lost in a
folder that is still there was published without being shown. reconfirmations()
now leaves a channel open when, besides what the answers touch, it lost a
position in a unit no answer names (a move: its own click, #258) or lost a header
name while a folder still there no longer gives a saved name at the channel's
position (reconcile records that per unit as headers_changed). Publication then
refuses R_CONFIRMED_ID_CHANGED, and the line beside Save names the channel and
the folder. A header name held only by the removed folder stays the removal's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
…lder (#279)

The lost positions were compared under the published folder names and every
unit a rename touched was then set aside as answered, so channels re-ordered
inside a renamed member folder were re-confirmed by the rename unseen (the
move list does not show them either). Positions are now compared after the
renames, and only units the user confirmed removed lose theirs by the answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
… states no names (#279)

A unit whose rewritten header states no channel names at all (header_names
None, not one empty name) was skipped, so a removal elsewhere still accepted
the saved name that folder lost, unseen. Such a header gives no saved name and
now counts like an empty name: the ID stays refused with its reason beside Save.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc

Copy link
Copy Markdown
Contributor Author

Independent review of a50cc93, with two fixes pushed (now 9a8a25e)

Reproduced from a fresh clone

  • tests/test_reconfirm_scope.py on main's src: both test_a_removal_does_not_accept_a_header_name_lost_in_a_folder_still_there cases fail. The two guards pass. All four pass on a50cc93.
  • python tools/check.py on a50cc93: ruff passed, 437 files formatted, 5290 passed, 4 skipped, code rules passed. The skips are the 4 environment-only ones.
  • Byte-identical publishes, main vs this branch (SHA-256 of morphobricks.toml, experiment_design.csv and morphobricks.records.jsonl): first save, header rename, confirmed removal, confirmed move, and move plus removal. All five are identical, before and after the fixes below.
  • Records files were checked with MorphoBricks-Spec main b4b1008 tools/audit/refrecords.py check: move "2 records OK"; removal, move plus removal, and the two saving tests of this PR "7 records OK".

Adversarial paths tried (notebook unless noted)

  • Two removals plus a lost name in ctrl/rep1: refused, and the reason shows beside Save.
  • A lost name, then re-tagging a different channel only: refused, and the reason shows beside Save.
  • A move plus a removal: unchanged from Fixes #258, fixes #252 #278. The move's own click is still needed.
  • RGB: no path. Colour units carry R/G/B, which have no header names.

Fixed: one commit each, and each regression test fails before its fix

  1. e13e933. Rename plus move inside the renamed folder (pure layer, reconfirmations).
    • Lost positions were compared under the published folder names. Every unit a rename touched was then set aside as answered.
    • So channels re-ordered inside a renamed member folder were accepted by the rename, and channel_moves does not list them either.
    • Positions are now compared after the renames. Only units confirmed removed lose their positions by the answer.
    • Test: test_spec_reconcile.py::test_a_member_folder_rename_does_not_accept_a_move_inside_that_folder.
    • Today the notebook and CLI never state folder renames, so users could not reach this path yet.
  2. 7c5b718. Hidden publish in the notebook: a header that states no names at all.
    • Scenario: ctrl/rep1 is saved as "Hoechst" and its file is rewritten with no channel names (header_names None, not one empty name). drug/rep2 is removed, ctrl/rep1 is re-tagged, and Yes, remove is clicked.
    • On a50cc93 this published with a DNA record from the removal. _headers_changed returned () for None.
    • None now counts like an empty name, which matches the PR's own rule ("another name, or none").
    • Test: a new no-header-names case of the PR's parametrized test.
  3. 9a8a25e updates the CHANGELOG entry.

Gate on 9a8a25e: ruff passed, 437 files formatted, 5292 passed, 4 skipped (the same 4 environment skips), code rules passed. Spec modules: tests/test_spec_*.py 1733 passed, 0 skipped.

For the owner. Not fixed, because they are design limits rather than small defects.

  • A switch to another saved name still publishes under a removal. Scenario: ctrl/rep1 is saved as "Hoechst" and now says "DAPI", which is also a saved DNA name. drug/rep2 is removed and ctrl/rep1 re-tagged. The removal accepts the loss of "Hoechst". The published file keeps no header names per folder, so this cannot be told apart from the legitimate case this PR keeps ("Hoechst" held only by the removed folder). The only ways to close it are refusing every header-name loss that comes with a removal (which would also refuse that legitimate case) or a Spec-level way to record names per folder.
  • Folders that never had names. If the removed folder held a channel's only names, then any tag action in the removal session drops those names, and Save is refused with "... lost a saved header name in " for folders that never had one. That message is misleading. The way out is to undo the tag. With fix 2 this now applies whether the files have no channel names at all or empty ones; before, it applied only to empty names. I judged the safe side right, but the wording could be improved.
  • Already on main, outside this PR. No confirmation exists for a lost header name. After saving the removal without a re-tag, the removed folder's names stay in the file. Any later re-tag of that channel is then refused for good (R_CONFIRMED_ID_CHANGED, "undo that change"). Reproduced on 9a8a25e. This may need its own issue.

Generated by Claude Code

@guijacquemet
guijacquemet merged commit 2d1ab30 into main Nov 11, 2026
7 checks passed
@guijacquemet
guijacquemet deleted the claude/reconfirm-scope branch November 11, 2026 05:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A removal or rename answer accepts any change of a channel ID it touches, including a lost header name elsewhere

2 participants